import webpy
import re
import os.path
from mimetypes import guess_type
import sys
import time
from urllib import unquote, quote

render = webpy.template.render(os.getenv('HOME')+'/.superkaramba/horovod/plugins/webserver/templates/') #, cache=False)
files = {} # pairs of "secret" prefix : (pretty-filename, real-filename)
settings = {'dosstack':[int(time.time())-601]} # denial-of-service timer trigger is offset by 10 minutes.
# we only allow 5 failed add attempts in 10 minutes.

class index:
	def GET(self, args=None):
		'''
		Here we test if the arguments match the file/secret pairs in the DB
		If they do, we serve the file, if not, generic information page.
		As a bonus, while serving the file, there are 2 modes:
		- pagemode, where we serve a page, with a link to the file
		  ( very useful for browsers that want to open the file, instead of saving it.
		- mime mode, where we serve the actual file.
		All the requesting party has to do is enter the appropriate URL, Examples:
		 http://123.456.789.123:8080/secretnumber/filename.ext.html - for pagemode
		 http://123.456.789.123:8080/secretnumber/filename.ext - for mime mode
		I suggest giving the pagemode link to the other party and suggesting to
		remove the ".html" manually if they want direct link.
		'''
		global render
		global files
		global settings

		if not args:
			message = 'No public files availabe.'
			print render.template('Personal file server',message)
		else:
			try:
				secret, filetag = args.split('/',1)
			except:
				secret = None
				filetag = None

			pagemode = False # serving a html page instead of the direct file.
			if secret:
				if filetag.lower().endswith('.html'):
					pagemode = True
					filetag = filetag[:-5] # cutting out the .html part
			try:
				if files[secret][0] == filetag:
					filename = files[secret][1]
				else:
					filename = None
			except:
				filename = None

			if filename and pagemode:
				# serving a web-page with a link to the file.
				message = '<a href="'+filetag+'">'+filetag+'</a>'
				print render.template('Hello',message)
			elif filename and (not pagemode):
				try:
					size = float(os.path.getsize(filename))
				except:
					size = 0
				if size:
					webpy.header("Content-Type", guess_type(filename)[0])
					webpy.header("Content-Length", size)
					print 'horovodservefile:'+filename
				else:
					print render.template('Sorry','priblem with file '+filename) # 'File not found')
			else:
				print render.template('Sorry','File not found')

class addfile:
	def GET(self, args=None):
		'''
		Funciton that adds servable file entries to the DB
		We expect the args to be:
		(in addition to http://localhost:port/add/)
		[adminsecretprefix/][hmac-hash/]files_serial_number/filetag=realfilename
		Note, realfilename may be uuencoded / quoted etc.
		This is the most dangerous function. If the correct secret and hash key
		were guessed, the attacker can add any local file to the serving db
		and read it.
		As a key-guessing-prevention measure, we only allow 5 failed ADD attempts within 10 minutes.
		If we detect more than 5 failed ADD's in 10 mins, we just ignore the ADDs
		until the 1st of the 5 failed attempts falls outside of NOW-10Minutes range.
		'''
		global render
		global files
		global settings
		# so, the plan:
		# 1. Test if we pass adminsecretprefix 
		# 2. Test if we pass hmac-hash test
		# 3. disassemble the fileinfo into secret, filetag, realpath
		# 4. add file to db.
		# if at any stage the test fails, args are set to None
		
		message = ''
		if (int(time.time())-settings['dosstack'][0]) > 600:
			# this means, if the last 5th failed ADD attempt was
			# more than 10 minutes ago, we work as normal.
		
			# 1st, we quote the args back. the parser automatically unquotes them, but our security hash was done on quoted string.
			args = quote(args)
			if args and settings['admsecret']:
				args = settings['admsecretfilter'](args)
			if args and settings['admkey']:
				args = settings['admkeytest'](args)
			if not args:
				# here we time the last # of failed add attempts and
				# throttle the connection if too many bad entries in last # of minutes
				settings['dosstack'].append(int(time.time()))
				if settings['dosstack'].__len__() > 5:
					trash = settings['dosstack'].pop()
			else:
				secret, filepair, filetag, filepath = None, None, None, None
				# this will not work properly if file secret is not used. It will find 1st / in pathname and split pathname apart.
				try:
					secret, fileopts = args.split('/',1)
					filetag, filepath = fileopts.split('=',1)
				except:
					pass
				if secret and filetag and filepath:
					# decode filepath if you encoded it. We quote encode both, path and 
					filepath = unquote(filepath)
					filetag = unquote(filetag)
					message = "Item %s added" % (filetag)
					files[secret]=(filetag, filepath)
		print render.template('Adding File', message)

class removefile:
	def GET(self, args=None):
		'''
		Removing the file from serving db.
		Here we only need the serial number to remove it.
		(in addition to http://localhost:port/remove/)
		[adminsecretprefix/][hmac-hash/]files_serial_number
		'''
		global render
		global files
		global settings
		# so, the plan:
		# 1. Test if we pass adminsecretprefix 
		# 2. Test if we pass hmac-hash test
		# 3. disassemble the fileinfo into secret, filetag, realpath
		# 4. add file to db.
		# if at any stage the test fails, args are set to None
		if args and settings['admsecret']:
			args = settings['admsecretfilter'](args)
		if args and settings['admkey']:
			args = settings['admkeytest'](args)
		message = "Not found"
		if args:
			try:
				del files[args]
				message = "OK"
			except:
				pass
		print render.template('Removing File', message)

class admin:
	def GET(self, args=None):
		'''
		I don't know what we are doing here.
		'''
		global render
		global settings
		message = 'OK'
		if args and settings['admsecret']:
			args = settings['admsecretfilter'](args)
		if args and settings['admkey']:
			args = settings['admkeytest'](args)
		if args in ['quit','exit','stop']:
			pass
		print render.template('Admin Page', message)

class status:
	def GET(self, args=None):
		global render
		global files
		message = "None"
		print render.template('Status',message)

########################
# set up functions
########################
def parse_options():
	import getopt
	global settings

	args = sys.argv[2:]
	i = 0
	settings['admsecret'] = None
	settings['admkey'] = None
	while i < args.__len__():
		if args[i] == '-s':
			try:
				settings['admsecret'] = args[i+1]
			except:
				pass
		elif args[i] == '-k':
			try:
				settings['admkey'] = args[i+1]
			except:
				pass
		i += 1

def setup():
	global files, settings
	
	parse_options()
	
	settings['fileinfosig'] = '[a-zA-Z0-9]+/[^/]+=.+' # securitynum/filetag=real-path, example '43mW4r23T4/me.png=mytriptosweeden3.png
	# there are 2 layers of security: admsecret-prefix and fileinfo hashing with HMAC
	if settings['admkey']:
		import hmac
		def testhash(text, key=settings['admkey']):
			'''
			We will be in this function ONLY IF "-k [KEY]" was passed to the server
			Text comes in as "hash-as-set-of-hexadecimals/text"
			Returning text (minus the hash prefix) if hash is valid.
			Returnint None if hash is not valid.
			'''
			try:
				h, i = text.split('/',1)
			except:
				h, i = None, None
			text = None
			i = i.replace('%3D','=',1) # this line needs explaining:
			# in the client module we quote the filetag and filepath separately.
			# this means hash is run on real '=', not '%3D'.
			if i:
				# run the hash rutine on it and compare
				a = hmac.new(key, i)
				if h == a.hexdigest():
					text = i
			return text
		settings['admkeytest'] = testhash
	else:
		settings['admkeytest'] = lambda(text): text

	if settings['admsecret']:
		filt = re.compile('(?<=^'+settings['admsecret']+'/).+')
		def admsecretfilter(text, filt=filt):
			#print 'In secret-processor '+text+'<p>'
			# if we are here, but fail bellow, we got ourselves a fake
			try:
				text = filt.search(text).group()
				#print 'In secret-processor stage 2 '+text+'<p>'
			except:
				text = None
			return text
		settings['admsecretfilter'] = admsecretfilter
	else:
		settings['admsecretfilter'] = lambda(text): text

	urls = (
		'/add/(.*)','addfile',
		'/remove/(.*)','removefile',
		'/(.*)', 'index',
		)
		# '/admin/(.*)','admin','/test/(.*)','test','/status/(.*)','status',
	webpy.run(urls, globals())

# webpy.webapi.internalerror = webpy.debugerror
if __name__ == "__main__": setup()
